package numericalMethods;

import java.util.ArrayList;
import java.util.LinkedList;

import Functions.Element;

import operations.FunctionOperations;


public class NewtonRaphson 
{
	//givens
	private double defaultDelta ;
	private double defaultEpsilon;
	private int defaultIterationsNumber;
	private double initial;
	
	//required
	private int maxNumOfIteration;
	private double approxRoot;
	private double presection;
	private double error;
	private double relerror;
	private double boundError;
	private FunctionOperations myFunction;
	
	
	public NewtonRaphson()
	{
		
	}

	
	
	public int getMaxNumOfIteration() {
		return maxNumOfIteration;
	}



	public double getApproxRoot() {
		return approxRoot;
	}



	public double getPresection() {
		return presection;
	}



	public double getError() {
		return error;
	}



	public double getRelerror() {
		return relerror;
	}


	

	 public double errorBound() {
         ErrorBound e = new ErrorBound();
         double error = e.calculateErrorBound(maxNumOfIteration, myFunction,
                         initial, 0, approxRoot - presection, approxRoot + presection);
         return error;
 }



	public  double[][] newtonRaphson(String function , double initialApproximation , 
			double newDelta,double newEpsilon , int maxIterations)
	{
		ArrayList<Double> k = new ArrayList<Double>();
		ArrayList<Double> Pk = new ArrayList<Double>();
		ArrayList<Double> fPK = new ArrayList<Double>();
		init(newDelta, newEpsilon, maxIterations);
		//hena null ----------------------------------------
		myFunction = new FunctionOperations(function);
		LinkedList<Element> diff = myFunction.differentiate();
		FunctionOperations df = new FunctionOperations(diff);
		//-----------------------------------------
		 initial = initialApproximation;
	    approxRoot = initialApproximation;
		double functionValue =myFunction.evaluate(approxRoot);
		for(maxNumOfIteration=0;maxNumOfIteration<defaultIterationsNumber;maxNumOfIteration++)
		{
			k.add((double)maxNumOfIteration);
			Pk.add(approxRoot);
			fPK.add(functionValue);
			double diffValue =df.evaluate(approxRoot);
			if(diffValue > Double.MIN_VALUE)
			{
				double nextPK = approxRoot - (functionValue/diffValue);
				double nextError = Math.abs(nextPK - approxRoot);
				boundError = nextError / Math.pow(error, 2);
				error = nextError;
				relerror = (2*error)/(Math.abs(nextPK)+defaultDelta);
				approxRoot= nextPK;
				functionValue = myFunction.evaluate(approxRoot);
				presection = Math.abs(functionValue);
				if( error<defaultDelta || relerror < defaultDelta || presection<defaultEpsilon)
				{
					maxNumOfIteration = maxNumOfIteration+1;
					k.add((double)maxNumOfIteration);
					Pk.add(approxRoot);
					fPK.add(functionValue);
					break;
				}
			}
			else
			{
				break;
			}
		}
		 double[][] table = new double[k.size()][3];
         double[] a1 = copy(k);
         double[] a2 = copy(Pk);
         double[] a3 = copy(fPK);

         for (int i = 0; i < table.length; i++) {

                 table[i][0] = a1[i];
                 table[i][1] = a2[i];
                 table[i][2] = a3[i];
         }
		return table;
	}
	
	private void init(double newDelta,double newEpsilon , int maxIterations)
	{
		if(newDelta==-1 && newEpsilon ==-1 && maxIterations==-1)
		{
			defaultIterationsNumber = 50;
			defaultDelta = .00001;
			defaultEpsilon = .00001;
		}
		else if(newDelta!=-1 && newEpsilon !=-1 && maxIterations!=-1)
		{
			defaultIterationsNumber = maxIterations;
			defaultDelta = newDelta;
			defaultEpsilon = newEpsilon;
		}
		else if(newDelta==-1 && newEpsilon !=-1 && maxIterations!=-1)
		{
			defaultIterationsNumber = maxIterations;
			defaultDelta = newEpsilon;
			defaultEpsilon =  newEpsilon;
		}
		else
		{
			System.out.println("bad Inputs");
		}
	}
	
	private double[] copy(ArrayList<Double> array) {
		double[] a = new double[array.size()];
		for (int i = 0; i < array.size(); i++) {
			a[i] = array.get(i);
		}
		return a;
	}
	

	public static void main(String[] args) 
	{
		NewtonRaphson s = new NewtonRaphson();
		double[][] answer=s.newtonRaphson("x^3-3*x+2",-2.4, -1, -1, -1);
		for(int i=0;i<answer[0].length;i++)
		{
			System.out.println(answer[0][i]);
		}
		System.out.println("---------------");
		for(int i=0;i<answer[1].length;i++)
		{
			System.out.println(answer[1][i]);
		}
		System.out.println("------------------------");
		for(int i=0;i<answer[2].length;i++)
		{
			System.out.println(answer[2][i]);
		}
		System.out.println("approx "+s.getApproxRoot());
		System.out.println("error "+s.getError());
		System.out.println("maxNum "+s.getMaxNumOfIteration());
		System.out.println("relError "+s.getRelerror());
		System.out.println("pre "+s.getPresection());
		System.out.println("bound "+s.errorBound());
	}

}
